package cn.felord.app.security.filter;

import cn.felord.app.data.Rest;
import cn.felord.app.data.RestBody;
import cn.felord.app.security.*;
import cn.felord.app.util.ResponseUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.GenericFilterBean;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

/**
 * jwt token刷新器 必须前置 于jwt验证器{@link JwtAuthenticationFilter}
 *
 * @author Dax
 * @since 13 :19  2019-04-10
 */
@Slf4j
public class RefreshJwtTokenFilter extends GenericFilterBean {
    private static final String AUTHENTICATION_PREFIX = "Bearer ";

    private final String REFRESH_TOKEN_ENTRY_POINT;
    private JwtTokenGenerator jwtTokenGenerator;

    private JwtTokenStorage jwtTokenStorage;
    private JwtPairBuilder jwtPairBuilder;

    /**
     * token 如果刷新失败 前端进行重新登录流程处理.
     *
     * @param refreshTokenEntryPoint the refresh token entry point
     * @param jwtTokenGenerator      the jwt token generator
     * @param jwtTokenStorage        the jwt token storage
     * @param jwtPairBuilder       the jwt entity builder
     */
    public RefreshJwtTokenFilter(String refreshTokenEntryPoint, JwtTokenGenerator jwtTokenGenerator, JwtTokenStorage jwtTokenStorage, JwtPairBuilder jwtPairBuilder) {

        this.REFRESH_TOKEN_ENTRY_POINT = refreshTokenEntryPoint;
        this.jwtTokenGenerator = jwtTokenGenerator;
        this.jwtTokenStorage = jwtTokenStorage;
        this.jwtPairBuilder = jwtPairBuilder;
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {

        HttpServletRequest httpServletRequest = (HttpServletRequest) request;

        String uri = httpServletRequest.getRequestURI();

        if (REFRESH_TOKEN_ENTRY_POINT.equals(uri) && HttpMethod.POST.name().equals(httpServletRequest.getMethod())) {
            String header = httpServletRequest.getHeader(HttpHeaders.AUTHORIZATION);
            if (StringUtils.hasText(header)) {
                String jwtRefreshToken = header.replace(AUTHENTICATION_PREFIX, "");

                JwtClaims jwtClaims = checkRefreshToken(jwtRefreshToken);

                Map<String, Object> map = new HashMap<>(5);
                map.put("time", LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));

                Rest rest;

                if (Objects.nonNull(jwtClaims)) {
                    JwtPair jwtPair = jwtPairBuilder.newJwtPair(jwtClaims, jwtTokenGenerator, jwtTokenStorage);
                    map.put("access_token", jwtPair.getAccessToken());
                    map.put("refresh_token", jwtPair.getRefreshToken());
                    rest = RestBody.okData(map, "refresh  success");
                } else {
                    rest = RestBody.failureData(map, "refresh failure", "-9999");
                }

                ResponseUtil.responseJsonWriter((HttpServletResponse) response, rest);

            }
        } else {
            chain.doFilter(request, response);
        }

    }


    /**
     * Check refresh token boolean.
     *
     * @param jwtRefreshToken the jwt refresh token
     * @return the jwtClaims
     */
    private JwtClaims checkRefreshToken(String jwtRefreshToken) {

        if (StringUtils.hasText(jwtRefreshToken)) {
            JwtClaims jwtClaims = jwtTokenGenerator.decodeAndVerify(jwtRefreshToken);
            if (Objects.nonNull(jwtClaims)) {
                String userId = jwtClaims.getAud();
                String expireTime = jwtClaims.getExp();
                // 是否过期
                if (isExpired(expireTime)) {
                    log.info("refreshToken : {}  is  expired", jwtRefreshToken);

                }
                // 从缓存获取 token
                JwtPair jwtPair = jwtTokenStorage.get(userId);
                if (Objects.isNull(jwtPair)) {
                    log.info("refreshToken : {}  is  not in cache", jwtRefreshToken);
                }
                String refreshToken = jwtPair.getRefreshToken();

                if (jwtRefreshToken.equals(refreshToken)) {
                    return jwtClaims;
                }
            }
        }
        return null;
    }

    /**
     * 判断是否过期
     *
     * @param exp the exp
     * @return boolean
     */
    public boolean isExpired(String exp) {
        return LocalDateTime.now().isAfter(LocalDateTime.parse(exp, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
    }

}
